/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* 
 * This code was copied from the JME3 Terrain library with minor changes 
 * made.
 */

import worldgeneration.WorldGenerator;
import world.GenericTerrainMap;
import worldgeneration.Util;
import util.BMPFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import util.MeshInterpolator;
import parameters.*;

    void normalizeTerrain(float value, GenericTerrainMap map, int size) {
        float currentMin, currentMax;
        float height;

        currentMin = map.getAllValues()[0];
        currentMax = currentMin;

        //find the min/max values of the height fTemptemptempBuffer
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                if (map.getValueAtPoint(i, j) > currentMax) {
                    currentMax = map.getValueAtPoint(i, j);
                } else if (map.getValueAtPoint(i, j) < currentMin) {
                    currentMin = map.getValueAtPoint(i, j);
                }
            }
        }

        //find the range of the altitude
        if (currentMax <= currentMin) {
            Logger.getLogger("normalize terrain").log(Level.INFO, "current max <= current min");
            return;
        }

        height = currentMax - currentMin;

        //scale the values to a range of 0-255
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                map.setValueAtPoint(i, j, (((map.getValueAtPoint(i, j) - currentMin).div(height)) * value).toFloat());
            }
        }
    }
    GenericTerrainMap load(long seed, int size, float range, float persistence) {
        int size_plus_one = size + 1;
        GenericTerrainMap map = new GenericTerrainMap(size_plus_one);
        float[][] temp_buffer = new float[size_plus_one][size_plus_one];
        Random random = new Random(seed);
        
        temp_buffer[0][0] = random.nextFloat();
        temp_buffer[0][size_plus_one - 1] = random.nextFloat();
        temp_buffer[size_plus_one - 1][0] = random.nextFloat();
        temp_buffer[size_plus_one - 1][size_plus_one - 1] = random.nextFloat();
        
        float offset_range = range;
        int step_size = size_plus_one - 1;
        while (step_size > 1) {
            int[] next_coords = new int[2];
            next_coords[0] = 0;
            next_coords[1] = 0;
            while (next_coords != null) {
                next_coords = doSquareStep(temp_buffer, next_coords, step_size, offset_range, random, size_plus_one);
            }
            next_coords = new int[2];
            next_coords[0] = 0;
            next_coords[1] = 0;
            while (next_coords != null) {
                next_coords = doDiamondStep(temp_buffer, next_coords, step_size, offset_range, random, size_plus_one);
            }
            step_size = step_size.intdiv(2i);
            offset_range *= persistence;
        }

        //We remove the Top row and rightmost column of values since they would be water anyways
        //This way the maps are powers of two sized
        for (int i = 0; i < size_plus_one; i++) {
            for (int j = 0; j < size_plus_one; j++) {
                map.setValueAtPoint(i, j, temp_buffer[i][j]);
            }
        }
normalizeTerrain(255f, map, size_plus_one);
        return map;
    }
    int[] doSquareStep(float[][] temp_buffer, int[] coords, int step_size, float offset_range, Random random, int size) {
        float corner_average = 0;
        int x = coords[0];
        int y = coords[1];
        corner_average += temp_buffer[x][y];
        corner_average += temp_buffer[x + step_size][y];
        corner_average += temp_buffer[x + step_size][y + step_size];
        corner_average += temp_buffer[x][y + step_size];
        corner_average = corner_average.div(4.0f);
        float offset = getOffset(random, offset_range, coords, corner_average);
        temp_buffer[x + step_size.intdiv(2i)][y + step_size.intdiv(2i)] = corner_average + offset;

        // Only get to next square if the center is still in map
        if (x + (step_size * 3).intdiv(2i) < size) {
            int[] temp = new int[2];
            temp[0] = x + step_size;
            temp[1] = y;
            return temp;
        }
        if (y + (step_size * 3).intdiv(2i) < size) {
            int[] temp = new int[2];
            temp[0] = 0;
            temp[1] = y + step_size;
            return temp;
        }
        return null;
    }
    float getOffset(Random random, float offset_range, int[] coords, float average) {
        return 2 * (random.nextFloat() - 0.5F) * offset_range;
    }

    int[] doDiamondStep(float[][] temp_buffer, int[] coords, int step_size, float offset_range, Random random, int size) {
        int corner_nbr = 0;
        float corner_average = 0;
        int x = coords[0];
        int y = coords[1];
        
        int[] dxs = new int[4];
        dxs[0] = 0;
        dxs[1] = step_size.intdiv(2i);
        dxs[2] = step_size;
        dxs[3] = step_size.intdiv(2i);
        
        int[] dys = new int[4];
        dys[0] = 0;
        dys[1] = -step_size.intdiv(2i);
        dys[2] = 0;
        dys[3] = step_size.intdiv(2i);
        

        for (int d = 0; d < 4; d++) {
            int i = x + dxs[d];
            if (i < 0 || i > size - 1) {
                continue;
            }
            int j = y + dys[d];
            if (j < 0 || j > size - 1) {
                continue;
            }
            corner_average += temp_buffer[i][j];
            corner_nbr++;
        }
        corner_average = corner_average.div(corner_nbr.toFloat());
        float offset = getOffset(random, offset_range, coords, corner_average);
        temp_buffer[x + step_size.intdiv(2i)][y] = corner_average + offset;

        if (x + (step_size * 3).intdiv(2i) < size) {
            int[] temp = new int[2];
            temp[0] = x + step_size;
            temp[1] = y;
            return temp;
        }
        if (y + step_size.intdiv(2i) < size) {
            if (x + step_size == size - 1) {
                int[] temp = new int[2];
                temp[0] = -step_size.intdiv(2i);
                temp[1] = y + step_size.intdiv(2i);
                return temp;
            } else {
                int[] temp = new int[2];
                temp[0] = 0;
                temp[1] = y + step_size.intdiv(2i);
                return temp;
            }
        }
        return null;
    }

public class InitialValue extends MeshInterpolator.InitialValueGenerator {
    GenericTerrainMap map;
    public InitialValue (GenericTerrainMap map) {
        this.map = map;
    }
    
    public float getValueAtPoint(int x, int y, int mesh_size, int size) {
        return map.getValueAtPoint(x, y);
    }
}

float range = 0.6;
int mesh = globalparameters.getChild("terrain.elevation.mesh").getValueAsInt();
float persistence = scriptparameters.getChild("persistence").getValueAsFloat();
long seed = worldgenerator.getSeed();
int size = worldgenerator.getWorldSize();
InitialValue init = new InitialValue(load(seed, size, range, persistence));
GenericTerrainMap map = MeshInterpolator.perform(init, mesh, size);
normalizeTerrain(255f, map, size);
Util.smooth(map, scriptparameters.getChild("smoothnp").getValueAsFloat(), scriptparameters.getChild("smoothradius").getValueAsInt());
//map.saveAsBitmap("elevation.bmp");
map.setIsLoaded(true);
worldgenerator.setMap("elevation", map);

